package model;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class MovedorFantasma extends MovedorMovible {

    @Override
    public boolean mover(Escenario escenario, Movible movible) {
        Fantasma fantasma = (Fantasma) movible;
        ElemEscenario posFinal;
        ElemEscenario posicionActual = fantasma.getPosicion();
        ElemEscenario posicionAnterior = fantasma.getPosicionAnterior();

        if (posicionActual == escenario.getPosicionGuaridaFantasmas()) {
            //Asumi que la guarida de fantasmas estaba rodeada por paredes
            posFinal = getUnicaSalida(escenario, posicionActual);
            if (posFinal == null) {
                System.out.println("La guarida del fantasma no tiene SOLO 1 SALIDA. Error de Negocio");
                return false;
            }
        } else if (estaEnRangoDeCaza(escenario, fantasma)) {
            if (fantasma.esPresa()) {
                posFinal = getProximaPosicionDeEscape(escenario, fantasma);
            } else {
                fantasma.setEstuveEnEstadoDeCaza(true);
                fantasma.setUltimaPosicionVistoPacman(null);
                posFinal = getProximaPosicionDeCaza(escenario, fantasma);
            }
        } else if (fantasma.estuveEnEstadoDeCaza()) {
            if (fantasma.getUltimaPosicionVistoPacman() == null) fantasma.setUltimaPosicionVistoPacman(escenario.getPosicionActualPacman());
            posFinal = getProximaPosicionSegunMemoria(escenario, fantasma);
            if (posFinal == fantasma.getUltimaPosicionVistoPacman()) {
                fantasma.setEstuveEnEstadoDeCaza(false);
                fantasma.setUltimaPosicionVistoPacman(null);
            }
        } else if (esBifurcacion(escenario, posicionActual, posicionAnterior)) {
            posFinal = getPosicionRandom(escenario, posicionActual, posicionAnterior);
        } else if (esCaminoSinSalida(escenario, posicionActual, posicionAnterior)) {
            posFinal = posicionActual;
        } else {
            return moverProximaPosicion(escenario, fantasma);

        }

        return posFinal != null && fantasma.mover(posFinal);
    }

    private ElemEscenario getProximaPosicionEstimada(Escenario escenario, Fantasma fantasma, ElemEscenario posicionFinal) {
        //Algoritmo de aproximacion estimada desde la posicion del fantasma hacia la posicion final
        ElemEscenario posicionActual = fantasma.getPosicion();
        int cuadrante = escenario.getCuadrante(posicionFinal);
        ElemEscenario posicionAnterior = fantasma.getPosicionAnterior();
        Map<String, ElemEscenario> mapaPosicionesVecinasConCaminos = getMapaPosicionesVecinasConDirecciones(escenario, posicionActual, posicionAnterior);
        ElemEscenario posArriba = mapaPosicionesVecinasConCaminos.get(Constantes.ARRIBA);
        ElemEscenario posAbajo = mapaPosicionesVecinasConCaminos.get(Constantes.ABAJO);
        ElemEscenario posIzquierda = mapaPosicionesVecinasConCaminos.get(Constantes.IZQUIERDA);
        ElemEscenario posDerecha = mapaPosicionesVecinasConCaminos.get(Constantes.DERECHA);
        if (posicionActual == posicionAnterior) {
            return getUnicaSalida(escenario, posicionActual);
        }
        ElemEscenario posReturn = posicionActual;
        switch (cuadrante) {
            case 1:
                if (posArriba != null) posReturn = posArriba;
                else if (posIzquierda != null) posReturn = posIzquierda;
                else if (posDerecha != null) posReturn = posDerecha;
                else if (posAbajo != null) posReturn = posAbajo;
                break;
            case 2:
                if (posArriba != null) posReturn = posArriba;
                else if (posDerecha != null) posReturn = posDerecha;
                else if (posIzquierda != null) posReturn = posIzquierda;
                else if (posAbajo != null) posReturn = posAbajo;
                break;
            case 3:
                if (posAbajo != null) posReturn = posAbajo;
                else if (posIzquierda != null) posReturn = posIzquierda;
                else if (posDerecha != null) posReturn = posDerecha;
                else if (posArriba != null) posReturn = posArriba;
                break;
            case 4:
                if (posAbajo != null) posReturn = posAbajo;
                else if (posDerecha != null) posReturn = posDerecha;
                else if (posIzquierda != null) posReturn = posIzquierda;
                else if (posArriba != null) posReturn = posArriba;
                break;
        }

        return posReturn;
    }

    private ElemEscenario getProximaPosicionSegunMemoria(Escenario escenario, Fantasma fantasma) {
        return getProximaPosicion(escenario, fantasma, fantasma.getUltimaPosicionVistoPacman());
    }

    private ElemEscenario getProximaPosicionDeCaza(Escenario escenario, Fantasma fantasma) {
        return getProximaPosicion(escenario, fantasma, escenario.getPosicionActualPacman());
    }

    private ElemEscenario getProximaPosicion(Escenario escenario, Fantasma fantasma, ElemEscenario posicionFinal) {
        return PathFinder.getProximaPosicionMasCorta(escenario, fantasma.getPosicion(), posicionFinal);
    }

    private ElemEscenario getProximaPosicionDeEscape(Escenario escenario, Fantasma fantasma) {
        //Algoritmo inverso al getProximaPosicion() (Puedo usar el getProximaPosicion con una pos del extremo contario al cuadrante)
        int cuadrantePacman = escenario.getCuadrante(escenario.getPosicionActualPacman());
        ElemEscenario posMasOpuesta = null;
        switch (cuadrantePacman) {
            case 1:
                posMasOpuesta = getElemEscenario(escenario, escenario.getY() - 1, escenario.getX() - 1);
                break;
            case 2:
                posMasOpuesta = getElemEscenario(escenario, escenario.getY() - 1, 0);
                break;
            case 3:
                posMasOpuesta = getElemEscenario(escenario, 0, escenario.getX() - 1);
                break;
            case 4:
                posMasOpuesta = getElemEscenario(escenario, 0, 0);
                break;
        }
        return getProximaPosicionEstimada(escenario, fantasma, posMasOpuesta);
    }

    private boolean estaEnRangoDeCaza(Escenario escenario, Fantasma fantasma) {
        int radio = fantasma.getCasillerosParaPersecucion();
        ElemEscenario posicionPacman = escenario.getPosicionActualPacman();
        ElemEscenario posicionFantasma = fantasma.getPosicion();
        return posicionPacman.getX() <= posicionFantasma.getX() + radio &&
                posicionPacman.getX() >= posicionFantasma.getX() - radio &&
                posicionPacman.getY() <= posicionFantasma.getY() + radio &&
                posicionPacman.getY() >= posicionFantasma.getY() - radio;
    }

    private boolean moverProximaPosicion(Escenario escenario, Fantasma fantasma) {
        ElemEscenario posicionActual = fantasma.getPosicion();
        ElemEscenario posicionAnterior = fantasma.getPosicionAnterior();

        if (posicionActual == posicionAnterior) {
            return fantasma.mover(getUnicaSalida(escenario, posicionActual));
        }

        Map<ElemEscenario, Boolean> mapa = getMapaPosicionesVecinasConCaminosSinAnterior(escenario, posicionActual, posicionAnterior);

        return mapa.size() == 1 && fantasma.mover((ElemEscenario) mapa.keySet().toArray()[0]);
    }

    private boolean esCaminoSinSalida(Escenario escenario, ElemEscenario posicionActual, ElemEscenario posicionAnterior) {
        Map<ElemEscenario, Boolean> mapa = getMapaPosicionesVecinasConCaminosSinAnterior(escenario, posicionActual, posicionAnterior);

        return mapa.isEmpty();
    }

    private ElemEscenario getPosicionRandom(Escenario escenario, ElemEscenario posicionActual, ElemEscenario posicionAnterior) {
        Map<ElemEscenario, Boolean> mapa = getMapaPosicionesVecinasConCaminosSinAnterior(escenario, posicionActual, posicionAnterior);

        Random rnd = new Random();
        double s = mapa.size();
        int x = 0;
        for (int i = 0; i < 7; i++) {
            x = (int) (rnd.nextDouble() * s);
        }
        return (ElemEscenario) mapa.keySet().toArray()[x];
    }

    private boolean esBifurcacion(Escenario escenario, ElemEscenario posicionActual, ElemEscenario posicionAnterior) {
        Map<ElemEscenario, Boolean> mapa = getMapaPosicionesVecinasConCaminosSinAnterior(escenario, posicionActual, posicionAnterior);

        int cant = mapa.size();

        return cant > 1;
    }

    private Map<ElemEscenario, Boolean> getMapaPosicionesVecinasConCaminosSinAnterior(Escenario escenario, ElemEscenario posicionActual, ElemEscenario posicionAnterior) {
        Map<ElemEscenario, Boolean> mapa = getMapaPosicionesVecinasConCaminos(escenario, posicionActual);
        mapa.remove(posicionAnterior);
        return mapa;
    }

    private Map<ElemEscenario, Boolean> getMapaPosicionesVecinasConCaminos(Escenario escenario, ElemEscenario posicionActual) {
        ElemEscenario elemEscenarioArriba = getElemEscenario(escenario, posicionActual.getY() - 1, posicionActual.getX());
        ElemEscenario elemEscenarioAbajo = getElemEscenario(escenario, posicionActual.getY() + 1, posicionActual.getX());
        ElemEscenario elemEscenarioIzquierda = getElemEscenario(escenario, posicionActual.getY(), posicionActual.getX() - 1);
        ElemEscenario elemEscenarioDerecha = getElemEscenario(escenario, posicionActual.getY(), posicionActual.getX() + 1);
        Map<ElemEscenario, Boolean> mapa = new HashMap<ElemEscenario, Boolean>();
        if (elemEscenarioAbajo != null && elemEscenarioAbajo.isMovimientoValido()){
            if (elemEscenarioAbajo.isTransportable()){
                elemEscenarioAbajo = getElemEscenario(escenario,((Transportador)elemEscenarioAbajo).getyTransportador(),((Transportador)elemEscenarioAbajo).getxTransportador());
            }
            mapa.put(elemEscenarioAbajo, elemEscenarioAbajo.isMovimientoValido());
        }
        if (elemEscenarioArriba != null && elemEscenarioArriba.isMovimientoValido()){
            if (elemEscenarioArriba.isTransportable()){
                elemEscenarioArriba = getElemEscenario(escenario,((Transportador)elemEscenarioArriba).getyTransportador(),((Transportador)elemEscenarioArriba).getxTransportador());
            }
            mapa.put(elemEscenarioArriba, elemEscenarioArriba.isMovimientoValido());
        }
        if (elemEscenarioIzquierda != null && elemEscenarioIzquierda.isMovimientoValido()){
            if (elemEscenarioIzquierda.isTransportable()){
                elemEscenarioIzquierda = getElemEscenario(escenario,((Transportador)elemEscenarioIzquierda).getyTransportador(),((Transportador)elemEscenarioIzquierda).getxTransportador());
            }
            mapa.put(elemEscenarioIzquierda, elemEscenarioIzquierda.isMovimientoValido());

        }
        if (elemEscenarioDerecha != null && elemEscenarioDerecha.isMovimientoValido()) {
            if (elemEscenarioDerecha.isTransportable()){
                elemEscenarioDerecha = getElemEscenario(escenario,((Transportador)elemEscenarioDerecha).getyTransportador(),((Transportador)elemEscenarioDerecha).getxTransportador());
            }
            mapa.put(elemEscenarioDerecha, elemEscenarioDerecha.isMovimientoValido());
        }
        return mapa;
    }

    private Map<String, ElemEscenario> getMapaPosicionesVecinasConDirecciones(Escenario escenario, ElemEscenario posicionActual, ElemEscenario posicionAnterior) {
        ElemEscenario elemEscenarioArriba = getElemEscenario(escenario, posicionActual.getY() - 1, posicionActual.getX());
        ElemEscenario elemEscenarioAbajo = getElemEscenario(escenario, posicionActual.getY() + 1, posicionActual.getX());
        ElemEscenario elemEscenarioIzquierda = getElemEscenario(escenario, posicionActual.getY(), posicionActual.getX() - 1);
        ElemEscenario elemEscenarioDerecha = getElemEscenario(escenario, posicionActual.getY(), posicionActual.getX() + 1);
        Map<String, ElemEscenario> mapa = new HashMap<String, ElemEscenario>();
        if (elemEscenarioAbajo != null && elemEscenarioAbajo.isMovimientoValido() && elemEscenarioAbajo != posicionAnterior) {
            if (elemEscenarioAbajo.isTransportable()){
                elemEscenarioAbajo = getElemEscenario(escenario,((Transportador)elemEscenarioAbajo).getyTransportador(),((Transportador)elemEscenarioAbajo).getxTransportador());
            }
            mapa.put(Constantes.ABAJO, elemEscenarioAbajo);
        }
        if (elemEscenarioArriba != null && elemEscenarioArriba.isMovimientoValido() && elemEscenarioArriba != posicionAnterior) {
            if (elemEscenarioArriba.isTransportable()){
                elemEscenarioArriba = getElemEscenario(escenario,((Transportador)elemEscenarioArriba).getyTransportador(),((Transportador)elemEscenarioArriba).getxTransportador());
            }
            mapa.put(Constantes.ARRIBA, elemEscenarioArriba);
        }
        if (elemEscenarioIzquierda != null && elemEscenarioIzquierda.isMovimientoValido() && elemEscenarioIzquierda != posicionAnterior) {
            if (elemEscenarioIzquierda.isTransportable()){
                elemEscenarioIzquierda = getElemEscenario(escenario,((Transportador)elemEscenarioIzquierda).getyTransportador(),((Transportador)elemEscenarioIzquierda).getxTransportador());
            }
            mapa.put(Constantes.IZQUIERDA, elemEscenarioIzquierda);
        }
        if (elemEscenarioDerecha != null && elemEscenarioDerecha.isMovimientoValido() && elemEscenarioDerecha != posicionAnterior) {
            if (elemEscenarioDerecha.isTransportable()){
                elemEscenarioDerecha = getElemEscenario(escenario,((Transportador)elemEscenarioDerecha).getyTransportador(),((Transportador)elemEscenarioDerecha).getxTransportador());
            }
            mapa.put(Constantes.DERECHA, elemEscenarioDerecha);
        }
        return mapa;
    }

    private ElemEscenario getUnicaSalida(Escenario escenario, ElemEscenario posicionActual) {
        Map<ElemEscenario, Boolean> mapa = getMapaPosicionesVecinasConCaminos(escenario, posicionActual);

        int cant = mapa.size();

        if (cant != 1) {
            return null;
        }

        return (ElemEscenario) mapa.keySet().toArray()[0];
    }
}
